/* -------------------------------------------------------------------
 GeoVISTA Center (Penn State, Dept. of Geography)
 Java source file for the class GeoMap
 Copyright (c), 2002, GeoVISTA Center
 Licensed under Simplified BSD License
 Original Author: Frank Hardisty
 $Author: jmacgill $
 $Id: GeoMap.java,v 1.12 2004/12/03 19:26:34 jmacgill Exp $
 $Date: 2004/12/03 19:26:34 $
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc.,
 -------------------------------------------------------------------   */


package edu.psu.geovista.app.map;

import edu.psu.geovista.classification.ClassifierPicker;
import edu.psu.geovista.data.geog.DataSetForApps;
import edu.psu.geovista.symbolization.BivariateColorSchemeVisualizer;
import edu.psu.geovista.symbolization.BivariateColorSymbolClassification;
import edu.psu.geovista.symbolization.BivariateColorSymbolClassificationSimple;
import edu.psu.geovista.symbolization.ColorSymbolClassification;
import edu.psu.geovista.symbolization.event.ColorClassifierEvent;
import edu.psu.geovista.symbolization.event.ColorClassifierListener;
import edu.psu.geovista.ui.Fisheyes;
import edu.psu.geovista.ui.cursor.GeoCursors;
import edu.psu.geovista.ui.event.*;
import edu.psu.geovista.visclass.VisualClassifier;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.net.URL;

/**
 * This class handles the user state, like selection, pan, zoom, plus
 * symbolization options.
 *
 * MapCanvas does most of the work.
 */
public class GeoMap
    extends JPanel
    implements
//MouseListener, MouseMotionListener,
    ActionListener, SelectionListener, IndicationListener, DataSetListener,
    ColorClassifierListener, ColorArrayListener,
    SpatialExtentListener, ComponentListener,
    ConditioningListener, VariableSelectionListener {
  public static final int VARIABLE_CHOOSER_MODE_ACTIVE = 0;
  public static final int VARIABLE_CHOOSER_MODE_FIXED = 1;
  public static final int VARIABLE_CHOOSER_MODE_HIDDEN = 2;
  transient protected MapCanvas mapCan;
  transient protected VisualClassifier visClassOne;
  transient protected VisualClassifier visClassTwo;
  transient protected JToolBar mapTools;
  transient protected JPanel topContent;
  transient protected Cursor[] customCursors;
  transient protected GeoCursors cursors;
  transient protected BivariateColorSchemeVisualizer biViz;
  transient protected Dimension currSize;
  transient protected Fisheyes fisheyes;
  transient DataSetForApps dataSet;
  public GeoMap() {
    super();

    JPanel vcPanel = new JPanel();
    vcPanel.setLayout(new BoxLayout(vcPanel, BoxLayout.Y_AXIS));
    visClassOne = new VisualClassifier();
    visClassTwo = new VisualClassifier();
    visClassOne.setAlignmentX(Component.LEFT_ALIGNMENT);
    visClassTwo.setAlignmentX(Component.LEFT_ALIGNMENT);
    visClassOne.setVariableChooserMode(
        ClassifierPicker.VARIABLE_CHOOSER_MODE_ACTIVE);
    visClassTwo.setVariableChooserMode(
        ClassifierPicker.VARIABLE_CHOOSER_MODE_ACTIVE);
    visClassOne.addActionListener(this);
    visClassOne.setOrientationInParentIsX(true);
    visClassTwo.addActionListener(this);
    visClassTwo.setOrientationInParentIsX(false);


    vcPanel.add(visClassTwo);
    vcPanel.add(visClassOne);

    JPanel legendPanel = new JPanel();
    legendPanel.setLayout(new BoxLayout(legendPanel, BoxLayout.X_AXIS));
    biViz = new BivariateColorSchemeVisualizer();
    legendPanel.add(vcPanel);
    legendPanel.add(Box.createRigidArea(new Dimension(4, 2)));
    legendPanel.add(biViz);

    topContent = new JPanel();
    topContent.setLayout(new BoxLayout(topContent, BoxLayout.Y_AXIS));
    makeToolbar();
    legendPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
    mapTools.setAlignmentX(Component.LEFT_ALIGNMENT);
    topContent.add(legendPanel);
    topContent.add(mapTools);

    //note: uncomment the line below for animation panel stuff
    //vcPanel.add(this.makeAnimationPanel());
    cursors = new GeoCursors();
    this.setCursor(cursors.getCursor(GeoCursors.CURSOR_ARROW_SELECT));

    this.setLayout(new BorderLayout());
    this.add(topContent, BorderLayout.NORTH);
    mapCan = new MapCanvas();
    this.add(mapCan, BorderLayout.CENTER);
    this.mapCan.addIndicationListener(this);
    visClassOne.addColorClassifierListener(this);
    visClassTwo.addColorClassifierListener(this);
    this.addIndicationListener(biViz);

    visClassTwo.setHighColor(new Color(0, 150, 0)); //green
    this.currSize = new Dimension(this.getSize());
    this.fisheyes = new Fisheyes();
    this.fisheyes.setLensType(Fisheyes.LENS_HEMISPHERE);

    //this.colorClassifierChanged(new ColorClassifierEvent(visClassTwo,visClassTwo.getColorSymbolClassification()));
  }

  public JPanel makeAnimationPanel() {
    JPanel animationPanel = new JPanel();
    Dimension prefSize = new Dimension(100, 50);
    animationPanel.setSize(prefSize);
    animationPanel.setPreferredSize(prefSize);

    //animationPanel.setBorder(BorderFactory.createLineBorder(Color.blue));
    JLabel timeLabel = new JLabel("Time Step:");
    animationPanel.add(timeLabel);

    JSlider timeSlider = new JSlider(0, 10, 3);
    timeSlider.setPaintLabels(true);
    timeSlider.setPaintTicks(true);
    timeSlider.setPaintTrack(true);
    timeSlider.setMajorTickSpacing(1);
    timeSlider.createStandardLabels(2);

    animationPanel.add(timeSlider);

    return animationPanel;
  }

  public void makeToolbar() {
    mapTools = new JToolBar();

    //Dimension prefSize = new Dimension(100,10);
    //mapTools.setMinimumSize(prefSize);
    //mapTools.setPreferredSize(prefSize);
    JButton button = null;
    Class cl = this.getClass();
    URL urlGif = null;
    Dimension buttDim = new Dimension(20, 20);

    //first button
    try {
      urlGif = cl.getResource("resources/select16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter selection mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_SELECT));
        GeoMap.this.mapCan.setMode(MapCanvas.MODE_SELECT);
      }
    });
    mapTools.add(button);

    mapTools.addSeparator();

    //second button
    try {
      urlGif = cl.getResource("resources/ZoomIn16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter zoom in mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_ZOOM_IN));
        GeoMap.this.mapCan.setMode(MapCanvas.MODE_ZOOM_IN);

        //GeoMap.this.setCursor(new Cursor(Cursor.HAND_CURSOR));
      }
    });
    mapTools.add(button);

    //third button
    try {
      urlGif = cl.getResource("resources/ZoomOut16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter zoom out mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.mapCan.setMode(MapCanvas.MODE_ZOOM_OUT);
        GeoMap.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_ZOOM_OUT));
      }
    });
    mapTools.add(button);

    //fourth button
    try {
      urlGif = cl.getResource("resources/Home16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Zoom to full extent");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.mapCan.zoomFullExtent();
      }
    });
    mapTools.add(button);

    //fifth button
    try {
      urlGif = cl.getResource("resources/pan16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter pan mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMap.this.mapCan.setMode(MapCanvas.MODE_PAN);
      }
    });
    mapTools.add(button);
    //sixth button
    try {
      urlGif = cl.getResource("resources/excentric16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Excentric Labels");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMap.this.mapCan.setMode(MapCanvas.MODE_EXCENTRIC);
      }
    });
    mapTools.add(button);

    //seventh button
    try {
      urlGif = cl.getResource("resources/fisheye16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Fisheye Lens");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMap.this.mapCan.setMode(MapCanvas.MODE_FISHEYE);
      }
    });
    mapTools.add(button);
    //eighth button
    try {
      urlGif = cl.getResource("resources/magnifying16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Magnifiying Lens");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMap.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMap.this.mapCan.setMode(MapCanvas.MODE_PAN);
      }
    });

    mapTools.add(button);

  }
  //jin: called by visualClassifier when change classification
  public void actionPerformed(ActionEvent e) {
    Object src = e.getSource();
    String command = e.getActionCommand();
    String varChangedCommand = this.visClassOne.getClassPick().
        COMMAND_SELECTED_VARIABLE_CHANGED;

    if ( (src == this.visClassOne) && command.equals(varChangedCommand)) {
      int index = visClassOne.getCurrVariableIndex();
      //index++; dataSetForApps goodness
      if(index>=0){//fix bug by jin,there could be a chance when index=-1
        this.mapCan.setCurrColorColumnX(index);
      }
      else{
        this.mapCan.setCurrColorColumnX(0);
      }
    }
    else if ( (src == this.visClassTwo) &&
             command.equals(varChangedCommand)) {
      int index = visClassTwo.getCurrVariableIndex();
      //index++; dataSetForApps goodness
      if(index>=0){ //fix bug by jin, there could be a chance when index=-1, it => DataSetForApps.getNumericDataAsDouble() throws exceptions
        this.mapCan.setCurrColorColumnY(index);
      }
      else{
        this.mapCan.setCurrColorColumnY(0);
      }
    }

  }

  public void selectionChanged(SelectionEvent e) {
    mapCan.selectionChanged(e);
  }

  public void conditioningChanged(ConditioningEvent e) {
    mapCan.setConditionArray(e.getConditioning());
  }

  public void indicationChanged(IndicationEvent e) {
    Object source = e.getSource();

    if ( (source == this.mapCan) && (e.getIndication() >= 0)) {
        //jin: for debug purpose
        int xClass = e.getXClass();
        int yClass = e.getYClass();
        this.visClassOne.setIndicatedClass(xClass);
        this.visClassTwo.setIndicatedClass(yClass);

      //this.fireIndicationChanged(e.getIndication());
    }
    else if ( (source == this.mapCan) && (e.getIndication() < 0)) {
      this.visClassOne.setIndicatedClass( -1);
      this.visClassTwo.setIndicatedClass( -1);
      this.biViz.indicationChanged(new IndicationEvent(this, -1));

      //this.fireIndicationChanged(e.getIndication());
    }
    else {
      mapCan.indicationChanged(e);
    }
  }

  public void spatialExtentChanged(SpatialExtentEvent e) {
    mapCan.spatialExtentChanged(e);
  }

  public void dataSetChanged(DataSetEvent e) {
    //mapCan.dataSetChanged(e);
    this.setDataSet(e.getDataSet());
  }

  public void variableSelectionChanged(VariableSelectionEvent e){
    visClassOne.setCurrVariableIndex(e.getVariableIndex()+1);
    mapCan.setCurrColorColumnX(e.getVariableIndex()+1);
  }

  public void colorArrayChanged(ColorArrayEvent e) {
    if (this.mapCan == null || e.getColors() == null) {
      return;
    }
    this.mapCan.setObservationColors(e.getColors());
  }

  public void colorClassifierChanged(ColorClassifierEvent e) {
    if (e.getSource() == this.visClassOne) {
      e.setOrientation(e.SOURCE_ORIENTATION_X);
    }

    if (e.getSource() == this.visClassTwo) {
      e.setOrientation(e.SOURCE_ORIENTATION_Y);
    }

    this.biViz.colorClassifierChanged(e);

    if (this.mapCan == null) {
      return;
    }

    //ColorSymbolClassification colorSymbolizer = e.getColorSymbolClassification();
    //Object src = e.getSource();
    ColorSymbolClassification colorSymbolizerX = this.visClassOne.
        getColorClasser();
    ColorSymbolClassification colorSymbolizerY = this.visClassTwo.
        getColorClasser();

    BivariateColorSymbolClassificationSimple biColorSymbolizer =
        new BivariateColorSymbolClassificationSimple();
    //turn these around to match what happens in the
    //scatterplot
    biColorSymbolizer.setClasserX(colorSymbolizerX.getClasser());
    biColorSymbolizer.setColorerX(colorSymbolizerX.getColorer());

    biColorSymbolizer.setClasserY(colorSymbolizerY.getClasser());
    biColorSymbolizer.setColorerY(colorSymbolizerY.getColorer());
    this.mapCan.setBivarColorClasser(biColorSymbolizer);
  }

  public void setXVariable(int var) {
    this.visClassOne.setCurrVariableIndex(var);
  }

  public void setYVariable(int var) {
    this.visClassTwo.setCurrVariableIndex(var);
  }

  public void setXChooserMode(int chooserMode) {
    this.visClassOne.setVariableChooserMode(chooserMode);
  }

  public void setYChooserMode(int chooserMode) {
    this.visClassTwo.setVariableChooserMode(chooserMode);
  }

  public void setBivarColorClasser(BivariateColorSymbolClassification
                                   bivarColorClasser) {
    this.mapCan.setBivarColorClasser(bivarColorClasser);
  }

  public void setSelectedObservations(int[] selObs) {
    this.mapCan.setSelectedObservationsInt(selObs);
  }

  public int[] getSelectedObservations() {
    return this.mapCan.getSelectedObservationsInt();
  }

  public Color[] getColors() {
    return this.mapCan.getColors();
  }

  public BivariateColorSymbolClassification
      getBivariateColorSymbolClassification() {
    return this.biViz.getBivariateColorClassification();
  }
    public void setAuxiliarySpatialData(DataSetForApps dataSet) {
		this.mapCan.setAuxiliarySpatialData(dataSet);

	}
      public void setDataSet(DataSetForApps dataSet) {
		//dataSet.addTableModelListener(this); //jin temp remove as have not update DataSetForApps
		this.dataSet = dataSet;
		this.mapCan.setDataSet(dataSet);
		/* jin temp remove 
		this.visClassOne.setDataSet(dataSet);
		this.visClassTwo.setDataSet(dataSet);*/

		// set default data to get color from
		int numNumeric = dataSet.getNumberNumericAttributes();

		int currColorColumnX = this.mapCan.getCurrColorColumnX();
		int currColorColumnY = this.mapCan.getCurrColorColumnY();

		if (currColorColumnX < 0) {
			if (numNumeric > 0) {
				int index = visClassOne.getCurrVariableIndex();
				// this.setXVariable(1);
				// this.mapCan.setCurrColorColumnX(1); // jin: set the initial
				// variable index of visualclassifier
				this.setXVariable(index);
				this.mapCan.setCurrColorColumnX(index);
			}
		} else if (currColorColumnX < numNumeric) {
			this.setXVariable(currColorColumnX);
			this.mapCan.setCurrColorColumnX(currColorColumnX);
		}

		if (currColorColumnY < 0) {
			if (numNumeric > 1) {

				int index = visClassTwo.getCurrVariableIndex();
				// this.setYVariable(2);
				// this.mapCan.setCurrColorColumnY(2); // jin: set the initial
				// variable index of visualclassifier
				this.setYVariable(index);
				this.mapCan.setCurrColorColumnY(index);
			} else if (numNumeric == 1) {
				this.setYVariable(1);
				this.mapCan.setCurrColorColumnY(1);
			}
		} else if (currColorColumnY < numNumeric) {
			setYVariable(currColorColumnY);
			this.mapCan.setCurrColorColumnY(currColorColumnY);
		}

		// Jin: fix the bug the initially the variable names in comboxes not
		// mataching the variable names in the indication

		if (numNumeric > 2) { // frank: fix the bug of the map crashing if
			// there are not enough variables
			visClassOne.setCurrVariableIndex(1); // Jin
			visClassTwo.setCurrVariableIndex(2);
			// frank here again... lets try something else....
			ActionEvent e = new ActionEvent(this, 0,
					ClassifierPicker.COMMAND_SELECTED_VARIABLE_CHANGED);
			visClassOne.actionPerformed(e);
			visClassTwo.actionPerformed(e);
		}
	}
  public void setDataSet(Object[] data) {

    this.mapCan.setDataSet(data);
    this.visClassOne.setData(data);
    this.visClassTwo.setData(data);



    //set default data to get color from
    DataSetForApps dataSet = new DataSetForApps();
    dataSet.setDataObject(data);

    int numNumeric = dataSet.getNumberNumericAttributes();

    int currColorColumnX = this.mapCan.getCurrColorColumnX();
    int currColorColumnY = this.mapCan.getCurrColorColumnY();

    if (currColorColumnX < 0) {
      if (numNumeric > 0) {
        int index = visClassOne.getCurrVariableIndex();
        //this.setXVariable(1);
        //this.mapCan.setCurrColorColumnX(1);    // jin: set the initial variable index of visualclassifier
          this.setXVariable(index);
        this.mapCan.setCurrColorColumnX(index);
      }
    }
    else if (currColorColumnX < numNumeric) {
      this.setXVariable(currColorColumnX);
      this.mapCan.setCurrColorColumnX(currColorColumnX);
    }

    if (currColorColumnY < 0) {
      if (numNumeric > 1) {

          int index = visClassTwo.getCurrVariableIndex();
        //this.setYVariable(2);
        //this.mapCan.setCurrColorColumnY(2);       // jin: set the initial variable index of visualclassifier
          this.setYVariable(index);
        this.mapCan.setCurrColorColumnY(index);
      }
      else if (numNumeric == 1) {
        this.setYVariable(1);
        this.mapCan.setCurrColorColumnY(1);
      }
    }
    else if (currColorColumnY < numNumeric) {
      setYVariable(currColorColumnY);
      this.mapCan.setCurrColorColumnY(currColorColumnY);
    }

     //Jin: fix the bug the initially the variable names in comboxes not mataching the variable names in the indication
     visClassOne.setCurrVariableIndex(1);     //Jin
     visClassTwo.setCurrVariableIndex(2);

  }

  public void setAuxiliarySpatialData(Object[] data) {
    mapCan.setAuxiliarySpatialData(data);
  }

  public void setBackground(Color bg) {
    if ( (mapCan != null) && (bg != null)) {
      this.mapCan.setBackground(bg);
    }
  }

  //start component listener stuff
  public void componentHidden(ComponentEvent e) {
  }

  public void componentMoved(ComponentEvent e) {
  }

  public void componentResized(ComponentEvent e) {
    //todo: zoom in or out a bit

  }

  public void componentShown(ComponentEvent e) {
  }

  //end component listener stuff

  /**
   * adds an IndicationListener
   */
  public void addIndicationListener(IndicationListener l) {
    this.mapCan.addIndicationListener(l);
  }

  /**
   * removes an IndicationListener from the component
   */
  public void removeIndicationListener(IndicationListener l) {
    this.mapCan.removeIndicationListener(l);
  }

  /**
   * adds an SelectionListener
   */
  public void addSelectionListener(SelectionListener l) {
    this.mapCan.addSelectionListener(l);
  }

  /**
   * removes an SelectionListener from the component
   */
  public void removeSelectionListener(SelectionListener l) {
    this.mapCan.removeSelectionListener(l);
  }

  /**
   * adds an SpatialExtentListener
   */
  public void addSpatialExtentListener(SpatialExtentListener l) {
    this.mapCan.addSpatialExtentListener(l);
  }

  /**
   * removes an SpatialExtentListener from the component
   */
  public void removeSpatialExtentListener(SpatialExtentListener l) {
    listenerList.remove(SpatialExtentListener.class, l);
    this.mapCan.removeSpatialExtentListener(l);
  }

  /**
   * implements ColorClassifierListener
   */
  public void addColorClassifierListener(ColorClassifierListener l) {
    this.visClassOne.addColorClassifierListener(l);
    this.visClassTwo.addColorClassifierListener(l);

  }

  /**
   * removes an ColorClassifierListener from the component
   */
  public void removeColorClassifierListener(ColorClassifierListener l) {
    this.visClassOne.removeColorClassifierListener(l);
    this.visClassTwo.removeColorClassifierListener(l);
  }


}

